#include <graphics/Font.h>
#include <graphics/TextureManager.h>
#include <graphics/Engine.h>
#include <system/Log.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#undef __FTERRORS_H__
#define FT_ERRORDEF(e, v, s) {e, s},
#define FT_ERROR_START_LIST  {
#define FT_ERROR_END_LIST    {0, 0}};
const struct
{
    int code;
    const char *message;
} FT_Errors[] =
#include FT_ERRORS_H

namespace ne
{
    Font::Font(ResourceManager *pManager,
               const uint64_t id,
               const std::string &name,
               const std::string &fileName,
               const bool isManual,
               ManualResourceLoader *pLoader)
        : Resource(pManager, id, name, fileName, isManual, pLoader),
          mFontType(FT_TRUETYPE),
          mFontHintType(FHT_NORMAL),
          mFontSize(0),
          mWidth(0),
          mHeight(0),
          mIsFixedWidth(false),
          mInternalTextureCreated(false)
    {
    }

    void Font::createInternalFont(const TexturePtr &pTexture,
                                  const FontType ft, const uint32_t fontSize,
                                  const uint32_t width, const uint32_t height,
                                  const FontHintType hint)
    {
        freeInternalFont();

        mpTexture = pTexture;
        mFontType = ft;
        mFontHintType = hint;
        mFontSize = fontSize;
        mWidth = width;
        mHeight = height;

        if (mFontSize > 32)
        {
            mFontSize = 32;
        }
    }

    void Font::freeInternalFont()
    {
        mFontType = FT_TRUETYPE;
        mFontHintType = FHT_NORMAL;
        mFontSize = 0;
        mWidth = 0;
        mHeight = 0;
        mIsFixedWidth = false;

        // FIXME:
        if (mInternalTextureCreated && mpTexture.notNull())
        {
            if (TextureManager::GetSingletonPtr())
            {
                TextureManager::GetSingleton().removeByID(mpTexture->getID());
            }
        }

        mInternalTextureCreated = false;
        mpTexture.setNull();

        destroyAllGlyphs();

        mFamilyName.clear();
        mStyleName.clear();
    }

    Glyph* Font::createGlyph(const utf32_t c)
    {
        GlyphMapConstIterator it = mGlyphMap.find(c);
        if (it != mGlyphMap.end()) return it->second;

        Glyph *glyph = new Glyph;
        glyph->code = c;
        glyph->width = 0.0f;
        glyph->height = 0.0f;
        glyph->horiBearingX = 0.0f;
        glyph->horiBearingY = 0.0f;
        glyph->horiAdvance = 0.0f;
        glyph->vertBearingX = 0.0f;
        glyph->vertBearingY = 0.0f;
        glyph->vertAdvance = 0.0f;

        mGlyphMap[c] = glyph;
        return glyph;
    }

    Glyph* Font::getGlyph(const utf32_t c) const
    {
        GlyphMapConstIterator it = mGlyphMap.find(c);
        if (it != mGlyphMap.end())
        {
            return it->second;
        }
        return 0;
    }

    bool Font::destroyGlyph(const utf32_t c)
    {
        GlyphMapIterator it = mGlyphMap.find(c);
        if (it != mGlyphMap.end())
        {
            delete it->second;
            mGlyphMap.erase(it);
        }
        return false;
    }

    void Font::destroyAllGlyphs()
    {
        for (GlyphMapIterator it=mGlyphMap.begin(); it!=mGlyphMap.end(); ++it)
        {
            delete it->second;
        }
        mGlyphMap.clear();
    }

    size_t Font::getGlyphNum() const
    {
        return mGlyphMap.size();
    }

    FontType Font::getFontType() const
    {
        return mFontType;
    }

    FontHintType Font::getFontHintType() const
    {
        return mFontHintType;
    }

    uint32_t Font::getFontSize() const
    {
        return mFontSize;
    }

    uint32_t Font::getFontWidth() const
    {
        return mWidth;
    }

    uint32_t Font::getFontHeight() const
    {
        return mHeight;
    }

    const std::string& Font::getFamilyName() const
    {
        return mFamilyName;
    }

    const std::string& Font::getStyleName() const
    {
        return mStyleName;
    }

    const TexturePtr& Font::getTexture() const
    {
        return mpTexture;
    }

    void Font::renderText2D(const std::string &text, const Vector3 &v,
                            const real width, const real height,
                            const Color &color, const real textSpace)
    {
        if (mpTexture.notNull() && text.length() > 0)
        {
            Unicode unicode(text);
            renderText2D(unicode, v, width, height, color, textSpace);
        }
    }

    /**
        01--------11
        |          |
        |  Screen  |
        |          |
        00--------10
    */
    void Font::renderText2D(const Unicode &text, const Vector3 &v,
                            const real width, const real height,
                            const Color &color, const real textSpace)
    {
        if (mpTexture.notNull() && text.length() > 0)
        {
            glEnable(mpTexture->getType());
            mpTexture->createRenderTexture();
            glBindTexture(mpTexture->getType(), mpTexture->getSource());
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
            glColor4fv(color.ptr());

            Vector3 pos(v);
            Vector4 offset;
            size_t length = text.length();
            size_t i = 0;
            while (i < length)
            {
                Glyph *glyph = getGlyph(text[i]);
                if (glyph)
                {
                    offset.x = pos.x + glyph->horiBearingX;
                    offset.y = pos.y + glyph->horiBearingY - glyph->height;
                    offset.z = pos.x + glyph->horiBearingX + glyph->width;
                    offset.w = pos.y + glyph->horiBearingY;

                    /** TODO: Face
                        1----3
                        |\   |
                        | \  |
                        |  \ |
                        |   \|
                        0----2
                    */
                    glBegin(GL_TRIANGLES);
                        // bottom-left
                        glTexCoord2f(glyph->texCoords[0].x, glyph->texCoords[0].y);
                        glVertex3f(offset.x, offset.y, pos.z);
                        // bottom-right
                        glTexCoord2f(glyph->texCoords[1].x, glyph->texCoords[0].y);
                        glVertex3f(offset.z, offset.y, pos.z);
                        // top-left
                        glTexCoord2f(glyph->texCoords[0].x, glyph->texCoords[1].y);
                        glVertex3f(offset.x, offset.w, pos.z);

                        // top-right
                        glTexCoord2f(glyph->texCoords[1].x, glyph->texCoords[1].y);
                        glVertex3f(offset.z, offset.w, pos.z);
                        // top-left
                        glTexCoord2f(glyph->texCoords[0].x, glyph->texCoords[1].y);
                        glVertex3f(offset.x, offset.w, pos.z);
                        // bottom-right
                        glTexCoord2f(glyph->texCoords[1].x, glyph->texCoords[0].y);
                        glVertex3f(offset.z, offset.y, pos.z);
                    glEnd();
                }

                if (glyph)
                {
                    pos.x += glyph->horiAdvance;
                }
                else
                {
                    pos.x += mWidth;
                }
                ++i;
            }
#endif
            glDisable(mpTexture->getType());
        }
    }

    real Font::getDefaultGlyphOffset(const FontLayout layout) const
    {
        switch (layout)
        {
            case FL_TOP_TO_BOTTOM:
            case FL_BOTTOM_TO_TOP:
            case FL_VERTICAL:
            {
                return mHeight;
            }

            default:
            {
                return mWidth;
            }
        }
    }

    real Font::getGlyphOffset(const utf32_t c, const FontLayout layout) const
    {
        Glyph *glyph = getGlyph(c);
        if (glyph)
        {
            switch (layout)
            {
                case FL_TOP_TO_BOTTOM:
                case FL_BOTTOM_TO_TOP:
                case FL_VERTICAL:
                {
                    return glyph->vertAdvance;
                }

                default:
                {
                    return glyph->horiAdvance;
                }
            }
        }
        return 0;
    }

    bool Font::genGlyphGeometry(const utf32_t c, const DynamicGeometryPtr &pGeometry,
                                const Vector3 &pos, const Vector3 &xAxis, const Vector3 &yAxis,
                                const Color &color, const FontLayout layout)
    {
        if (pGeometry.notNull())
        {
            Glyph *glyph = getGlyph(c);
            if (glyph)
            {
                /** TODO: Face
                    1----3
                    |\   |
                    | \  |
                    |  \ |
                    |   \|
                    0----2
                */
                uint32_t index = pGeometry->getVertexCount();

                switch (layout)
                {
                    case FL_RIGHT_TO_LEFT:
                    {
                        Vector2 offsets[2];
                        offsets[0].x = - glyph->width;
                        offsets[0].y = glyph->horiBearingY - glyph->height;
                        offsets[1].x = 0;
                        offsets[1].y = glyph->horiBearingY;
                        pGeometry->addPosition(pos + xAxis * offsets[0].x + yAxis * offsets[0].y);
                        pGeometry->addPosition(pos + xAxis * offsets[0].x + yAxis * offsets[1].y);
                        pGeometry->addPosition(pos + xAxis * offsets[1].x + yAxis * offsets[0].y);
                        pGeometry->addPosition(pos + xAxis * offsets[1].x + yAxis * offsets[1].y);
                        break;
                    }

                    case FL_TOP_TO_BOTTOM:
                    case FL_VERTICAL:
                    {
                        Vector2 offsets[2];
                        offsets[0].x = glyph->vertBearingX;
                        offsets[0].y = - glyph->vertBearingY - glyph->height;
                        offsets[1].x = glyph->vertBearingX + glyph->width;
                        offsets[1].y = - glyph->vertBearingY;
                        pGeometry->addPosition(pos + xAxis * offsets[0].x + yAxis * offsets[0].y);
                        pGeometry->addPosition(pos + xAxis * offsets[0].x + yAxis * offsets[1].y);
                        pGeometry->addPosition(pos + xAxis * offsets[1].x + yAxis * offsets[0].y);
                        pGeometry->addPosition(pos + xAxis * offsets[1].x + yAxis * offsets[1].y);
                        break;
                    }

                    case FL_BOTTOM_TO_TOP:
                    {
                        Vector2 offsets[2];
                        offsets[0].x = glyph->vertBearingX;
                        offsets[0].y = glyph->vertAdvance - glyph->vertBearingY - glyph->height;
                        offsets[1].x = glyph->vertBearingX + glyph->width;
                        offsets[1].y = glyph->vertAdvance - glyph->vertBearingY;
                        pGeometry->addPosition(pos + xAxis * offsets[0].x + yAxis * offsets[0].y);
                        pGeometry->addPosition(pos + xAxis * offsets[0].x + yAxis * offsets[1].y);
                        pGeometry->addPosition(pos + xAxis * offsets[1].x + yAxis * offsets[0].y);
                        pGeometry->addPosition(pos + xAxis * offsets[1].x + yAxis * offsets[1].y);
                        break;
                    }

                    default:
                    {
                        Vector2 offsets[2];
                        offsets[0].x = glyph->horiBearingX;
                        offsets[0].y = glyph->horiBearingY - glyph->height;
                        offsets[1].x = glyph->horiBearingX + glyph->width;
                        offsets[1].y = glyph->horiBearingY;
                        pGeometry->addPosition(pos + xAxis * offsets[0].x + yAxis * offsets[0].y);
                        pGeometry->addPosition(pos + xAxis * offsets[0].x + yAxis * offsets[1].y);
                        pGeometry->addPosition(pos + xAxis * offsets[1].x + yAxis * offsets[0].y);
                        pGeometry->addPosition(pos + xAxis * offsets[1].x + yAxis * offsets[1].y);
                        break;
                    }
                }

                pGeometry->addColor(color);
                pGeometry->addColor(color);
                pGeometry->addColor(color);
                pGeometry->addColor(color);

                pGeometry->addTexCoord(glyph->texCoords[0]);
                pGeometry->addTexCoord(ne::Vector2(glyph->texCoords[0].x, glyph->texCoords[1].y));
                pGeometry->addTexCoord(ne::Vector2(glyph->texCoords[1].x, glyph->texCoords[0].y));
                pGeometry->addTexCoord(glyph->texCoords[1]);

                pGeometry->addIndex(index);
                pGeometry->addIndex(index + 2);
                pGeometry->addIndex(index + 1);
                pGeometry->addIndex(index + 3);
                pGeometry->addIndex(index + 1);
                pGeometry->addIndex(index + 2);
                return true;
            }
        }
        return false;
    }

    void Font::updateFont()
    {
    }

    Font::~Font()
    {
        unload();
    }

    bool Font::loadImplement()
    {
        FT_Library library;
        FT_Error error;
        
        error = FT_Init_FreeType(&library);
        if (error)
        {
            Log::Out(LL_WARNING,
                "Init FreeType Error (0x%02x) : [%s] for Font (%s) file (%s) fail",
                FT_Errors[error].code, FT_Errors[error].message,
                getName().c_str(), getFileName().c_str());
            return false;
        }

        FT_Face face;
        std::string fileName = getFileName();

        // Create face object
        error = FT_New_Face(library, fileName.c_str(), 0, &face);
        if (error)
        {
            fileName = Engine::GetSingleton().getWorkSpace() + getFileName();
            error = FT_New_Face(library, fileName.c_str(), 0, &face);
            if (error)
            {
                Log::Out(LL_WARNING,
                    "Create face Error (0x%02x) : [%s] for Font (%s) file (%s) fail",
                    FT_Errors[error].code, FT_Errors[error].message,
                    getName().c_str(), getFileName().c_str());
                FT_Done_FreeType(library);
                return false;
            }
        }

        // Select charmap
        error = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
        if (error)
        {
            Log::Out(LL_WARNING,
                "Select charmap Error (0x%02x) : [%s] for Font (%s) file (%s) fail",
                FT_Errors[error].code, FT_Errors[error].message,
                getName().c_str(), fileName.c_str());
            FT_Done_Face(face);
            FT_Done_FreeType(library);
            return false;
        }

        // Set character size
        // Resolution initial value is 72dpi
        // Normal resolution is 72dpi or 96dpi
        error = FT_Set_Char_Size(face, (FT_F26Dot6)(mFontSize << 6), 0, 72, 0);
        if (error)
        {
            Log::Out(LL_WARNING,
                "Set char size Error (0x%02x) : [%s] for Font (%s) file (%s) fail",
                FT_Errors[error].code, FT_Errors[error].message,
                getName().c_str(), fileName.c_str());
            FT_Done_Face(face);
            FT_Done_FreeType(library);
            return false;
        }

        FT_GlyphSlot slot = face->glyph;

        uint32_t loadRenderMask = FT_LOAD_RENDER;
        switch (mFontHintType)
        {
            case FHT_NORMAL:
            {
                loadRenderMask |= FT_LOAD_TARGET_NORMAL;
                break;
            }

            case FHT_LIGHT:
            {
                loadRenderMask |= FT_LOAD_TARGET_LIGHT;
                break;
            }

            case FHT_MONO:
            {
                loadRenderMask |= FT_LOAD_TARGET_MONO;
                break;
            }

            case FHT_LCD:
            {
                loadRenderMask |= FT_LOAD_TARGET_LCD;
                break;
            }

            case FHT_LCD_V:
            {
                loadRenderMask |= FT_LOAD_TARGET_LCD_V;
                break;
            }

            default:
            {
                loadRenderMask |= FT_LOAD_TARGET_NORMAL;
                break;
            }
        }

        // Calculate max Width ang Height
        uint32_t maxWidth = 0;
        uint32_t maxHeight = 0;
        uint32_t numGlyph = 0;
        uint32_t index = 0;
        uint32_t code = FT_Get_First_Char(face, &index);

        while (index != 0)
        {
            error = FT_Load_Char(face, code, loadRenderMask);
            if (!error && slot->format == ft_glyph_format_bitmap)
            {
                maxWidth = std::max(maxWidth, uint32_t(slot->bitmap.width));
                maxHeight = std::max(maxHeight, uint32_t(slot->bitmap.rows));
                ++numGlyph;
            }
            code = FT_Get_Next_Char(face, code, &index);
        }

        Log::Out(
            "Font (%s) load %u Glyphs from file (%s) with MaxSize[%u, %u]",
            getName().c_str(), numGlyph, fileName.c_str(), maxWidth, maxHeight);

        // Create Texture
        // TODO: Here need to fix calculate texture size
        uint32_t textureSize = uint32_t(std::sqrt(numGlyph)) + 1;
        uint32_t textureWidth = textureSize * maxWidth;
        uint32_t textureHeight = textureSize * maxHeight;
        TexturePtr pTexture(TextureManager::GetSingleton().createTexture(
            "Font_" + getName() + "_Texture",
            "", TT_2D, TF_ALPHA, PT_UNSIGNED_BYTE, textureWidth, textureHeight, 1));
        if (pTexture.isNull())
        {
            Log::Out(LL_WARNING,
                "Create Texture for Font (%s) file (%s) fail",
                getName().c_str(), fileName.c_str());
            FT_Done_Face(face);
            FT_Done_FreeType(library);
            return false;
        }

        Log::Out("Font (%s) from file (%s) create Texture[%u, %u]",
                 getName().c_str(), fileName.c_str(), textureWidth, textureHeight);

        createInternalFont(pTexture, FT_TRUETYPE, mSize,
            maxWidth, maxHeight, mFontHintType);

        mIsFixedWidth = FT_IS_FIXED_WIDTH(face) ? true : false;

        mFamilyName = face->family_name;
        mStyleName = face->style_name;

        uint32_t posX = 0;
        uint32_t posY = 0;
        index = 0;
        code = FT_Get_First_Char(face, &index);
        while (index != 0)
        {
            error = FT_Load_Char(face, code, loadRenderMask);
            FT_Bitmap *bitmap = &slot->bitmap;
            if (!error && slot->format == ft_glyph_format_bitmap)
            {
                if (posX + bitmap->width > textureWidth)
                {
                    posX = 0;
                    posY += maxHeight;
                }

                if (mFontHintType == FHT_MONO)
                {
                    byte *data = new byte[bitmap->width * bitmap->rows];
                    memset(data, 0, bitmap->width * bitmap->rows * sizeof(byte));
                    byte *buffer = bitmap->buffer;
                    for (int y=0; y<bitmap->rows; ++y)
                    {
                        for (int x=0; x<bitmap->width; ++x)
                        {
                            byte mask = 0x80 >> (x & 0x7);
                            byte bitPixel = buffer[x >> 3] & mask;
                            if (bitPixel)
                            {
                                data[y * bitmap->width + x] = 0xFF;
                            }
                        }
                        buffer += bitmap->pitch;
                    }
                    pTexture->loadData(data, PF_ALPHA, PT_UNSIGNED_BYTE,
                        posX, posY, 0, bitmap->width, bitmap->rows, 1);
                    delete[] data;
                }
                else
                {
                    pTexture->loadData(bitmap->buffer, PF_ALPHA, PT_UNSIGNED_BYTE,
                        posX, posY, 0, bitmap->width, bitmap->rows, 1);
                }

                Glyph *glyph = createGlyph(code);
                glyph->width = real(slot->metrics.width) / 64.0f;
                glyph->height = real(slot->metrics.height) / 64.0f;
                glyph->horiBearingX = real(slot->metrics.horiBearingX) / 64.0f;
                glyph->horiBearingY = real(slot->metrics.horiBearingY) / 64.0f;
                glyph->horiAdvance = real(slot->metrics.horiAdvance) / 64.0f;
                glyph->vertBearingX = real(slot->metrics.vertBearingX) / 64.0f;
                glyph->vertBearingY = real(slot->metrics.vertBearingY) / 64.0f;
                glyph->vertAdvance = real(slot->metrics.vertAdvance) / 64.0f;
                glyph->texCoords[0].x = real(posX) / textureWidth;
                glyph->texCoords[0].y = real(posY + bitmap->rows) / textureHeight;
                glyph->texCoords[1].x = real(posX + bitmap->width) / textureWidth;
                glyph->texCoords[1].y = real(posY) / textureHeight;

                posX += bitmap->width;
            }
            code = FT_Get_Next_Char(face, code, &index);
        }

        FT_Done_Face(face);
        FT_Done_FreeType(library);

        mInternalTextureCreated = true;

        return true;
    }

    bool Font::unloadImplement()
    {
        freeInternalFont();
        return true;
    }

    size_t Font::_calculateSize() const
    {
        if (mpTexture.notNull())
        {
            return mpTexture->getSize();
        }
        return 0;
    }
}
